#include <arpa/inet.h>
#include <unistd.h>
#include "socket_epoll.h"
#include "event.h"

namespace simple {
    SocketEpoll::SocketEpoll(int port) : port_(port) {}

    SocketEpoll::SocketEpoll(int port, int socketStyle) : port_(port), socket_style_(socketStyle) {}

    /**
     * 设置IO句柄非阻塞标志位，也就是NIO
    */
    int non_blocking(int fd) {
        int flags;

        // 获取当前的文件描述符标志
        if ((flags = fcntl(fd, F_GETFL, 0)) == -1) {
            return -1;
        }

        // 设置O_NONBLOCK标志
        return fcntl(fd, F_SETFL, flags |= O_NONBLOCK);
    }

    bool SocketEpoll::openSocket() {
        // 创建socketFD
        if ((this->listen_fd_ = socket(AF_INET, this->socket_style_, 0)) == -1) {
            spdlog::error("create socket fail, {}", strerror(errno));
            return false;
        }

        // 绑定socketFD到监听地址
        this->listen_addr_.sin_family = AF_INET;
        this->listen_addr_.sin_port = htons(this->port_);
        this->listen_addr_.sin_addr = in_addr{.s_addr=INADDR_ANY};
        if (bind(this->listen_fd_, (struct sockaddr *) &this->listen_addr_, sizeof(this->listen_addr_)) != 0) {
            spdlog::error("create socket fail, {}", strerror(errno));
            return false;
        }

        // 设置端口复用, 使得在关闭套接字后，端口可以立即被重用
        int so_reuseaddr = 1;
        if (setsockopt(this->listen_fd_, SOL_SOCKET, SO_REUSEADDR, &so_reuseaddr, sizeof(int)) == -1) {
            spdlog::error("socket(SO_REUSEADDR) failed, {}", strerror(errno));
            return false;
        }
        return true;
    }

    bool SocketEpoll::openListen() {
        if (this->listen_fd_ < 1) {
            spdlog::error("listen_fd is not available: {}", this->listen_fd_);
            return false;
        }
        if (listen(this->listen_fd_, 512) != 0) {
            spdlog::error("socket listen err: {}", strerror(errno));
            return false;
        }
        spdlog::info("Listening address {}:{}", inet_ntoa(this->listen_addr_.sin_addr), this->port_);
        return true;
    }

    bool SocketEpoll::openEpoll() {
        if ((this->epoll_fd_ = epoll_create(1)) == -1) {
            spdlog::error("create epoll_fd fail, {}", strerror(errno));
            return false;
        }
        return true;
    }

    bool SocketEpoll::registerEpoll() {
        this->listenSession_ = new Session(this->listen_fd_, &this->listen_addr_, nullptr);
        return this->registerEpoll(this->listen_fd_, this->listenSession_);
    }

    bool SocketEpoll::registerEpoll(int register_fd, Session *const session) {
        if (this->epoll_fd_ <= 0 || register_fd <= 0) {
            spdlog::error("register epoll_fd fail, epoll_fd={}, register_fd={}", this->epoll_fd_, register_fd);
            return false;
        }

        // 把io句柄注册到epoll
        auto *event_data = new epoll_event{.events=EPOLLIN | EPOLLHUP, .data={.ptr=(void *) session}};
        if (epoll_ctl(this->epoll_fd_, EPOLL_CTL_ADD, register_fd, event_data) == -1) {
            spdlog::error("register epoll_fd fail, {}", strerror(errno));
            return false;
        }
        // 设置io句柄非阻塞(nio)
        return (non_blocking(register_fd) == 0);
    }

    bool SocketEpoll::unRegisterEpoll() {
        if (this->listenSession_ != nullptr) {
            return this->unRegister_epoll(this->listenSession_);
        }
        return false;
    }

    bool SocketEpoll::unRegister_epoll(Session *session) {
        if (this->epoll_fd_ <= 0 || listen_fd_ <= 0) {
            spdlog::error("register epoll_fd fail, epoll_fd={}, listen_fd={}", this->epoll_fd_, this->listen_fd_);
            return false;
        }
        // 把io句柄从epoll注销
        if (epoll_ctl(this->epoll_fd_, EPOLL_CTL_DEL, session->socketFd(), nullptr) == -1) {
            spdlog::error("register epoll_fd err: {}", strerror(errno));
            return false;
        }
        delete session;
        return true;
    }

    void SocketEpoll::createSession() {
        auto client_addr = new sockaddr_in{};
        int addr_len = sizeof(sockaddr_in);
        int client_fd = ::accept(this->listen_fd_, (struct sockaddr *) client_addr, (socklen_t *) &addr_len);
        if (client_fd == -1 && errno == EAGAIN) {
            return;
        } else if (client_fd == -1) {
            spdlog::error("createSession err: {}", strerror(errno));
        } else {
            auto *clientSession = new Session(client_fd, &this->listen_addr_, client_addr);
            this->registerEpoll(client_fd, clientSession);
        }
    }

    void SocketEpoll::doAccept(epoll_event epollEvent) {
        auto session = (Session *) epollEvent.data.ptr;
        if (epollEvent.events & EPOLLIN) {
            // 读就绪
            Event::publishEvent(std::string("active"), session);
        } else if (epollEvent.events & EPOLLHUP) {
            // 关闭会话
            this->unRegister_epoll(session);
            ::close(epollEvent.data.fd);
        }
    }

    void SocketEpoll::setPort(int port) noexcept {
        this->port_ = port;
    }

    void SocketEpoll::setStyle(int style) noexcept {
        this->socket_style_ = style;
    }

    bool SocketEpoll::ready() {
        if (this->openSocket() && this->openListen()) {
            return (this->openEpoll() && this->registerEpoll());
        }
        return -1;
    }

    bool SocketEpoll::accept() {
        while (true) {
            int ready = epoll_wait(this->epoll_fd_, this->ready_event_buff_, this->ready_event_buff_size_, 20000);
            if (ready == -1 && errno == EINTR) {
                // 系统中断导致epoll_wait结束(调试、手动触发), 忽略中断
                continue;
            } else if (ready == -1) {
                // 监听异常
                spdlog::error("epoll_wait err: {}", strerror(errno));
                this->close();
                return false;
            } else if (ready == 0) {
                // 监听超时
                spdlog::info("waiting for connection");
                continue;
            }

            for (int i = 0; i < ready; ++i) {
                auto event = this->ready_event_buff_[i];
                if (this->listen_fd_ == ((Session *) event.data.ptr)->socketFd()) {
                    // 监听句柄就绪, 创建新会话
                    this->createSession();
                } else {
                    // 已有会话就绪, 处理会话
                    this->doAccept(event);
                }
            }
        }
    }

    void SocketEpoll::setHandler(const std::function<void(Session *)> &function) {
        this->handler_ = function;
    }

    void SocketEpoll::close() noexcept {
        this->unRegisterEpoll();
        ::close(this->epoll_fd_);
    }
}